package org.baicaixiaozhan.tenet.base;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class OptionalUtil<T> {

    private static final OptionalUtil<?> EMPTY = new OptionalUtil<>(null);

    private final boolean isPresent;
    private final T value;

    private OptionalUtil() {
        this.isPresent = false;
        this.value = null;
    }

    public static <T> OptionalUtil<T> empty() {
        @SuppressWarnings("unchecked")
        OptionalUtil<T> t = (OptionalUtil<T>) EMPTY;
        return t;
    }

    private OptionalUtil(T value) {
        if (value instanceof Collection) {
            this.isPresent = !(null == value || ((Collection) value).isEmpty());
        } else if (value instanceof Map) {
            this.isPresent = !(null == value || ((Map) value).isEmpty());
        } else {
            this.isPresent = !(null == value);
        }
        this.value = value;
    }

    public static <T> OptionalUtil<T> ofNullable(T value) {
        return new OptionalUtil<>(value);
    }

    public T get() {
        if (!isPresent) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    public boolean isPresent() {
        return isPresent;
    }

    public boolean isEmpty() {
        return !isPresent;
    }

    public void ifPresent(Consumer<T> action) {
        if (isPresent) {
            action.accept(value);
        }
    }

    public void ifPresentOrElse(Consumer<T> action, Runnable emptyAction) {
        if (isPresent) {
            action.accept(value);
        } else {
            emptyAction.run();
        }
    }

    public OptionalUtil<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent()) {
            return this;
        } else {
            return predicate.test(value) ? this : empty();
        }
    }

    public <U> OptionalUtil<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            return OptionalUtil.ofNullable(mapper.apply(value));
        }
    }

    public T orElse(T other) {
        return isPresent ? value : other;
    }

    public T orElseGet(Supplier<T> supplier) {
        return isPresent ? value : supplier.get();
    }

    public T orElseThrow() {
        if (!isPresent) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (isPresent) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }


}
